// 格子
class Grid {
    coordinates = [];

    // status = 'move' | 'fixed' | 'empty';
    baseStatus = 'empty';

    lastStatus = 'empty';

    gridElement = null;

    wrapper = null;

    get status() {
        return this.baseStatus;
    }

    set status(status) {
        this.lastStatus = this.baseStatus;
        this.baseStatus = status;
        this.render()
    }

    creatGrid(coordinates) {
        const [x, y] = coordinates;
        const grid = document.createElement('div');
        grid.classList.add(`grid_${this.baseStatus}`);
        grid.setAttribute('grid-id', `${x}:${y}`);
        return grid;
    }

    render() {
        const [x, y] = this.coordinates;
        const grid = this.wrapper.querySelector(`[grid-id="${x}:${y}"]`);
        grid.classList.replace(`grid_${this.lastStatus}`, `grid_${this.baseStatus}`);
    }

    constructor(coordinates, status = 'empty') {
        this.coordinates = coordinates;
        this.wrapper = document.getElementById('game-wrapper');
        this.baseStatus = status;
        this.gridElement = this.creatGrid(coordinates);
        return this
    }
}


// 方块
class Square {

    shape = [];

    dom = null;

    status = 'move' | 'fixed';

    x = 0;

    y = 0;

    container = null;

    shapeMap = new Map([
        [0, [[1, 1], [1, 1]]],
        [1, [[1], [1], [1], [1]]],
        [2, [[0, 1, 1], [1, 1, 0]]],
        [3, [[1, 1, 0], [0, 1, 1]]],
        [4, [[1, 0], [1, 0], [1, 1]]],
        [5, [[0, 1], [0, 1], [1, 1]]],
        [6, [[1, 1, 1], [0, 1, 0]]],
    ])

    game = null;

    constructor(game) {
        this.shape = this.generate();

        this.game = game;

        this.container = game.container;

        const { size } = game;
        this.x = Math.floor(size[0] / 2);
        this.y = size[1] - 1;

    }

    // 生成方块 包括dom（仅用于右侧预览）
    generate() {
        const type = Math.floor(Math.random() * 7);
        const square = this.shapeMap.get(type);

        const wrapper = document.createElement('div');
        wrapper.classList.add('next-wrapper');

        for (let i = square.length - 1; i >= 0; i--) {
            const line = document.createElement('div');
            line.classList.add('next-line');
            wrapper.appendChild(line);
            for (let j = 0; j < square[i].length; j++) {
                const grid = new Grid([j, i], square[i][j] ? 'move' : 'empty');
                line.appendChild(grid.gridElement);
            }
        }

        this.dom = wrapper;
        return square;
    }

    changeDirection() {
        // 逆时针旋转数组
        let lineLength = this.shape.length;
        let rowLength = this.shape[0].length;

        let isRotate = true;
        const newShape = new Array(rowLength).fill(0);
        for (let i = 0; i < rowLength; i++) {
            newShape[i] = [];
            for (let j = 0; j < lineLength; j++) {
                newShape[i].push(this.shape[lineLength - j - 1][i]);
            }
        }

        // 反转路径如果和fiexed有重合，不翻转
        this.traversalSquare({
            callback: ([y, x]) => {
                console.log(127, x, y, this.game.size[0])
                if (y >= this.game.size[1]) return;
                if (x < 0 || x >= this.game.size[0] || this.container[y][x].status === 'fixed') {
                    isRotate = false
                }
            }
        }, newShape);
        if (isRotate) {
            this.shape = newShape;
        }
    }

    // 下落
    move(direction = 'down') {
        const collisionDirection = this.checkCollision(this.shape);
        if (!(collisionDirection.includes('bottom') && direction === 'down')) {
            // 继续移动
            // 移除上一次的位置
            this.clear();
            switch (direction) {
                case 'up':
                    this.changeDirection();
                    break;
                case 'down':
                    this.y--;
                    break;
                case 'left':
                    if (collisionDirection.includes('left')) break;
                    this.x--;
                    break;
                case 'right':
                    if (collisionDirection.includes('right')) break;
                    this.x++;
                    break;
            }
            this.render('move');
        } else {
            // 产生碰撞
            this.fixed();

            // 判断是否可消除
            const elliminateLines = this.checkElliminate();
            if (elliminateLines) {
                this.handleElliminate(elliminateLines);
            }

            // 判断是否在最上层碰撞
            if (this.y === this.game.size[1] - 1) {
                this.game.gameOver();
            }

            // 生成下一个方块
            this.game.generateSquare();
        }
    }

    left() {
        // 向左移动
        this.move('left')
    }

    right() {
        this.move('right')
    }

    down() {
        // 向下移动
        this.move('down')
    }

    rotate() {
        // 旋转
        this.move('up')
    }

    resetGrid([y, x], status = 'empty') {
        if (this.edgeDetection([y, x])) return;
        this.container[y][x].status = status;
    }


    traversalSquare({ bottomCallback, topCallback, leftCallback, rightCallback, callback }, shape = this.shape) {
        const height = shape.length;
        const width = shape[0].length;
        for (let i = 0; i < shape.length; i++) { // 0-> shape.length 下 -> 上
            for (let j = 0; j < shape[i].length; j++) {
                if (shape[i][j]) {
                    if (i === 0) {
                        bottomCallback && bottomCallback([this.y, this.x + j]);
                    }
                    if (i === shape.length - 1) {
                        topCallback && topCallback([this.y + height - 1, this.x + j]);
                    }
                    if (j === 0) {
                        leftCallback && leftCallback([this.y + i, this.x]);
                    }
                    if (j === shape[i].length - 1) {
                        rightCallback && rightCallback([this.y + i, this.x + width - 1]);
                    }

                    callback && callback([this.y + i, this.x + j]);
                }
            }
        }
    }

    fixed() {
        this.status = 'fixed';
        this.traversalSquare({
            callback: ([x, y]) => {
                this.resetGrid([x, y], 'fixed');
            }
        })
    }

    // 边缘检测
    edgeDetection([y, x]) {
        if (y < 0 || y >= this.game.size[1]) return true;
        if (x < 0 || x >= this.game.size[0]) return true;
        return false;
    }


    clear() {
        this.traversalSquare({
            callback: (coordinates) => {
                this.resetGrid(coordinates);
            }
        })
    }

    render(status) {
        this.traversalSquare({
            callback: (coordinates) => {
                this.resetGrid(coordinates, status);
            }
        })
    }

    // 碰撞检测
    checkCollision() {
        const collisionDirection = new Set();

        this.traversalSquare({
            bottomCallback: ([y, x]) => {
                if (this.edgeDetection([y, x])) return;
                if (y === 0 || this.container[y - 1][x].status === 'fixed') {
                    collisionDirection.add('bottom');
                }
            },
            leftCallback: ([y, x]) => {
                if (this.edgeDetection([y, x])) return;
                if (x === 0 || this.container[y][x - 1].status === 'fixed') {
                    collisionDirection.add('left');
                }
                if (y === 0 || this.container[y - 1][x].status === 'fixed') {
                    collisionDirection.add('bottom');
                }
            },
            rightCallback: ([y, x]) => {
                if (this.edgeDetection([y, x])) return;
                if (x === this.game.size[0] - 1 || this.container[y][x + 1].status === 'fixed') {
                    collisionDirection.add('right');
                }
                if (y === 0 || this.container[y - 1][x].status === 'fixed') {
                    collisionDirection.add('bottom');
                }
            }
        });

        return Array.from(collisionDirection);
    }

    // 消除检测
    checkElliminate() {
        const line = [];
        for (let i = this.y; i < this.y + this.shape.length; i++) {
            if (i >= this.game.size[1]) return false;
            if (this.container[i].every(item => item.status === 'fixed')) { // 可消除
                line.push(i);
            }
        }
        return line.length ? line : false;
    }

    isLineSameStatus(line, status) {
        return this.container[line].every(item => item.status === status);
    }

    // 消除方块
    handleElliminate(lines) {
        const lowLine = lines[0];

        switch (lines.length) {
            case 1:
                this.game.score += 1;
                break;
            case 2:
                this.game.score += 3;
                break;
            case 3:
                this.game.score += 6;
                break;
            case 4:
                this.game.score += 10;
                break;
        }

        for (let i = lowLine; i < this.game.size[1]; i++) {
            if (this.isLineSameStatus(i, 'empty')) return;
            const nextLine = this.container.findIndex((item, index) => index > i && !this.isLineSameStatus(index, 'fixed'));
            this.container[i].forEach((grid, index) => grid.status = this.container[nextLine][index].status);
        }
    }

}

class Game {
    size = [10, 20];
    // 停止（包括失败） 暂停状态 开始状态
    // stop pauce start
    status = 'stop';

    baseScore = 0;

    startButton = null;

    pauceButton = null;

    restartButton = null;

    continueButton = null;

    get score() {
        return this.baseScore;
    }

    set score(num) {
        this.baseScore = num;
        const scoreDom = document.querySelector('.score');
        scoreDom.innerText = this.score;
    }

    // 容器 0 1 2 二维数组 数组内保存对应的Grid
    container = [];

    currentSquare = null;

    nextSquare = null;

    // 定时器频率(ms) 1000 \ 800 \ 600 \ 400 \ 200
    frequency = 800;

    get speed() {
        return 6 - this.frequency / 200;
    }

    set speed(num) {
        this.frequency = 1200 - num * 200;

        const speed = document.querySelector('.speed');
        speed.innerText = this.speed;

        if(this.status === 'start') {
            this.restartInterval();
        }
    }

    // 定时器
    timer = null;

    bootStrap() {
        this.status = 'start';

        // 开始生成方块
        this.generateSquare(true);

        // 方块开始下落
        this.squareDown();
        
        this.hiddenButton(this.startButton);
        this.showButton(this.pauceButton);
        this.hiddenButton(this.continueButton);
        this.showButton(this.restartButton);
    }

    pauce() {
        this.status = 'pauce';

        // 方块停止下落
        this.stopSquareDown();
        this.hiddenButton(this.startButton);
        this.hiddenButton(this.pauceButton);
        this.showButton(this.continueButton);
        this.showButton(this.restartButton);
    }

    continue() {
        this.status = 'start';

        // 方块开始下落
        this.squareDown();
        
        this.hiddenButton(this.startButton);
        this.showButton(this.pauceButton);
        this.hiddenButton(this.continueButton);
        this.showButton(this.restartButton);
    }

    restart() {
        this.score = 0;

        // 清除方块
        this.clearSquare();

        // 重置分数
        this.score = 0;

        this.bootStrap();

        
        this.hiddenButton(this.startButton);
        this.showButton(this.pauceButton);
        this.hiddenButton(this.continueButton);
        this.showButton(this.restartButton);
    }

    clearSquare() {
        for (let i = 0; i < this.size[1]; i++) {
            for (let j = 0; j < this.size[0]; j++) {
                this.container[i][j].status = 'empty';
            }
        }
    }

    gameOver() {
        this.status = 'stop';
        this.score = 0;
        this.stopSquareDown();
        alert('游戏结束');
        this.restart();

    }

    stopSquareDown() {
        clearInterval(this.timer);
    }

    renderWrapper(size, selector) {
        const element = document.querySelector(selector);
        const [width, height] = size;
        for (let i = height - 1; i >= 0; i--) {
            for (let j = 0; j < width; j++) {
                const grid = new Grid([i, j]);
                element.appendChild(grid.gridElement);
                this.container[i][j] = grid;
            }
        }
    }

    showButton(button) {
        button.classList.remove('hidden');
    }

    hiddenButton(button) {
        button.classList.add('hidden');
    }


    constructor() {
        this.container = (new Array(this.size[1]))
            .fill(1)
            .map(() => new Array(this.size[0]).fill(0));

        // 渲染格子
        this.renderWrapper(this.size, '#game-wrapper');

        const startButton = document.querySelector('[operate="start"]');
        const pauceButton = document.querySelector('[operate="pauce"]');
        const restartButton = document.querySelector('[operate="restart"]');
        const continueButton = document.querySelector('[operate="continue"]');
        this.startButton = startButton;
        this.pauceButton = pauceButton;
        this.restartButton = restartButton;
        this.continueButton = continueButton;

        
        this.hiddenButton(this.pauceButton);
        this.hiddenButton(this.restartButton);
        this.hiddenButton(this.continueButton);
        this.addEventListener();
    }

    generateSquare(init = false) {
        this.currentSquare = init ? new Square(this) : this.nextSquare;

        this.currentSquare.render('move');

        this.insertNextSquare();
    }

    squareDown() {
        this.timer = setInterval(() => {
            this.currentSquare.down();
        }, this.frequency);
    }

    restartInterval() {
        clearInterval(this.timer);
        this.squareDown();
    }


    changeSpeed(mode = 'up') {
        switch (mode) {
            case 'down':
                if (this.frequency < 1000) {
                    this.speed--;
                }
                break;
            case 'up':
                if (this.frequency > 200) {
                    this.speed++;
                }
                break;
        }
        if (this.frequency === 1000) {
            // 不允许减少
            const downButton = document.querySelector('.down');
            downButton.setAttribute('disabled', 'disabled');

        } else if (this.frequency === 200) {
            // 不允许增加
            const upButton = document.querySelector('.up');
            upButton.setAttribute('disabled', 'disabled');

        } else {
            // 都允许
            const downButton = document.querySelector('.down');
            const upButton = document.querySelector('.up');
            upButton.removeAttribute('disabled');
            downButton.removeAttribute('disabled');

        }
    }

    insertNextSquare() {
        // 插入下一个方块
        this.nextSquare = new Square(this);
        const nextWrapper = document.querySelector('.next-square');
        nextWrapper.removeChild(document.querySelector('.next-wrapper'));
        nextWrapper.appendChild(this.nextSquare.dom);
    }

    addEventListener() {
        document.addEventListener('keydown', (event) => {
            switch (event.key) {
                case 's':
                case 'ArrowDown':
                    this.currentSquare.down();
                    break;
                case 'w':
                case 'ArrowUp':
                    this.currentSquare.rotate();
                    break;
                case 'a':
                case 'ArrowLeft':
                    this.currentSquare.left();
                    break;
                case 'd':
                case 'ArrowRight':
                    this.currentSquare.right();
                    break;
            }
        })
    }

}

const game = new Game();
